/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#include "cc_compat.h"
#include "stdio.h"
#include "ps2.h"
#include "queue.h"
#include "private/ps2_private.h"
#include "timer.h"
#include "sdi.h"

// These buffers must be a power of 2 in size.
#define TP_QUEUE_DEPTH		16
#define KB_QUEUE_DEPTH		8
#define PS2_TX_QUEUE_DEPTH	16

int ps2_tx_queue_get(void);

#define PS2_SHOW_STATES			0
#define PS2_STATE_DISABLED		0
#define PS2_STATE_IDLE			1

static unsigned char xdata TP_Head;
static unsigned char xdata TP_Tail;					// point the current queue
static unsigned char xdata TP_Queue[TP_QUEUE_DEPTH];

static unsigned char xdata KB_Head;
static unsigned char xdata KB_Tail;					// point the current queue
static unsigned char xdata KB_Queue[KB_QUEUE_DEPTH];

static unsigned char xdata ps2_tx_head;
static unsigned char xdata ps2_tx_tail;
static unsigned int xdata ps2_tx_queue[PS2_TX_QUEUE_DEPTH];

static unsigned char xdata ps2_state;
static unsigned char xdata ps2_state_prev;

unsigned char xdata KB_error_flags;

data unsigned char ignore_ps2_data;
data unsigned char ps2_tx_cnt;
data unsigned char ps2_rx_cnt;


void ps2_module_init(void)
{
	ignore_ps2_data =	0;
	TP_Head = TP_Tail = 0;
	KB_Head = KB_Tail = 0;
	ps2_tx_head = ps2_tx_tail = 0;
	ps2_tx_cnt = 0;
	ps2_rx_cnt = 0;
}

void ps2_init(void)
{
	ps2_private_init();
	ps2_clear_error_flags();
}

void ps2_set_interrupt_ignore(void)
{
	ignore_ps2_data=1;
}

void ps2_clear_interrupt_ignore(void)
{
	ignore_ps2_data=0;
}

// This function is called from interrupt context
// its not safe to call it from anywhere else
// Its not static because its called from the files
// in the private repository
int ps2_tp_queue_add(unsigned char val)
{
	if (QUEUE_SIZE(TP_Head,TP_Tail) >= TP_QUEUE_DEPTH )
	{
		return QUEUE_FULL;
	}
	TP_Queue[TP_Head & (TP_QUEUE_DEPTH-1)] = val;
	TP_Head++;
	return 0;
}

int ps2_tp_queue_get(void)
{
	int retval;

	if (QUEUE_SIZE(TP_Head,TP_Tail) == 0 )
	{
		return QUEUE_EMPTY;
	}
	retval = TP_Queue[TP_Tail & (TP_QUEUE_DEPTH-1)];
	TP_Tail++;
	return retval;
}

unsigned char ps2_tp_queue_size(void)
{
#if 0
	if (KEY_QUEUE_SIZE)
	{
		print_tstamp();
		printf("<H:%d,T:%d>",(unsigned int)KeyHead,(unsigned int)KeyTail);
	}
#endif
	return QUEUE_SIZE(TP_Head,TP_Tail);
}

unsigned char ps2_tp_queue_full(void)
{
	return QUEUE_SIZE(TP_Head,TP_Tail) >= TP_QUEUE_DEPTH;
}

void ps2_tp_queue_clear(void)
{
	TP_Head = TP_Tail = 0;
}

// This function is called from interrupt context
// its not safe to call it from anywhere else
// Its not static because its called from the files
// in the private repository
int ps2_kb_queue_add(unsigned char val)
{
	if (QUEUE_SIZE(KB_Head,KB_Tail) >= (KB_QUEUE_DEPTH) )
	{
		return QUEUE_FULL;
	}
	KB_Queue[KB_Head & (KB_QUEUE_DEPTH-1)] = val;
	KB_Head++;
	return 0;
}

int ps2_kb_queue_get(void)
{
	int retval;

	if (QUEUE_SIZE(KB_Head,KB_Tail) == 0 )
	{
		return QUEUE_EMPTY;
	}
	retval = (int)KB_Queue[KB_Tail & (KB_QUEUE_DEPTH-1)];
	KB_Tail++;
	// printf("M: %x ",(unsigned int)retval);
	return retval;
}

unsigned char ps2_kb_queue_size(void)
{
	unsigned char size;

//	static unsigned kb_oldsize;

	size = QUEUE_SIZE(KB_Head,KB_Tail);

#if 0
	if (size != kb_oldsize)
	{
		kb_oldsize = size;
		print_tstamp();
		printf("\n<H:%d,T:%d>",(unsigned int)KB_Head,(unsigned int)KB_Tail);
	}
#endif
	return size;
}

void ps2_kb_queue_clear(void)
{
	KB_Head = KB_Tail = 0;
}
// This queue is special. Its in int but its actually only 15-bits
// My original queue code was only for 8-bit data.  But the ps2 TX system
// needs 4 extra bits to hold which of the ports you need to send this
// data to.
// Queue empty is -1 so the high bit is reserved.
// For KB3700 This means that this system
// cannot be used for ps2 port 3 only unless the dest bits git converted into a
// packed format (ie 000 = port 0, 001 = port 1, 010= port 2, etc...)
// The IO3731 is different than the KB3700 but the bits we use are still safe.

int ps2_tx_queue_add(unsigned char dest, unsigned char val)
{
	if (QUEUE_SIZE(ps2_tx_head,ps2_tx_tail) >= (PS2_TX_QUEUE_DEPTH) )
	{
		print_tstamp();
		printf("ps2full\n");
		return QUEUE_FULL;
	}
	ps2_tx_queue[ps2_tx_head & (PS2_TX_QUEUE_DEPTH-1)] = (((unsigned int)dest)<<8|val);
	ps2_tx_head++;
	return 0;
}

// This function is called from interrupt context.  Don't call it if
// interrupt routines are enabled.
int ps2_tx_queue_get(void)
{
	int retval;

	if (QUEUE_SIZE(ps2_tx_head,ps2_tx_tail) == 0 )
	{
		return QUEUE_EMPTY;
	}
	retval = (int)ps2_tx_queue[ps2_tx_tail & (PS2_TX_QUEUE_DEPTH-1)];
	ps2_tx_tail++;
	return retval;
}

unsigned char ps2_tx_queue_size(void)
{
	unsigned char size;

	size = QUEUE_SIZE(ps2_tx_head,ps2_tx_tail);
	return size;
}

void ps2_tx_queue_clear(void)
{
	ps2_tx_head = ps2_tx_tail = 0;
}

void ps2_cmd_touchpad_cmd(void)
{
	unsigned char cnt;
	for (cnt=0; cnt < SDI_CMD_ARGS; cnt++)
	{
		ps2_tx_queue_add(DEST_TOUCHPAD, SDI_CMD_ARG(cnt));
	}
}

void ps2_cmd_keyboard_cmd(void)
{
	unsigned char cnt;
	for (cnt=0; cnt < SDI_CMD_ARGS; cnt++)
	{
		ps2_tx_queue_add(DEST_KEYBOARD, SDI_CMD_ARG(cnt));
	}
}

void ps2_start(void)
{
	ps2_state = PS2_STATE_IDLE;
}

void ps2_process(void)
{
	unsigned char eflags;

#if PS2_SHOW_STATES
	if (ps2_state_prev != ps2_state)
	{
		ps2_state_prev = ps2_state;
		print_tstamp();
		printf("ps2=%d\n",
			   (unsigned int)ps2_state
			  );
	}
#endif

	switch (ps2_state)
	{
		case PS2_STATE_DISABLED:
			break;

		case PS2_STATE_IDLE:
			if (!ps2_tx_int_enabled())
			{
				if (ps2_tx_queue_size() > 0)
				{
					ps2_enable_tx_int();
					ps2_kick_tx();
				}
			}
			break;
	}

	// Right now I don't know what to do about ps2 errors
	// but I want to know if they happen.
	eflags = ps2_get_error_flags();
	if (eflags)
	{
		printf("ps/2 error %x\n",(unsigned int)eflags);
		ps2_clear_error_flags();
	}
}
